package com.autumn.util.excel.workbook;

import com.autumn.util.CollectionUtils;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.excel.WorkbookHeader;
import com.autumn.util.excel.WorkbookInfo;
import com.autumn.util.excel.column.AbstractColumn;
import com.autumn.util.excel.column.ColumnInfo;
import com.autumn.util.excel.enums.HorizontalAlignment;
import com.autumn.util.excel.exceptions.ExcelException;
import com.autumn.util.excel.export.AbstractExportInfo;
import com.autumn.util.excel.export.ExportAdapterInfo;
import com.autumn.util.tuple.TupleTwo;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 工作表导出
 */
public class WorkbookExport {
    /**
     * 导出最大行数
     */
    private static final int MAX_ROW_COUNT = Short.MAX_VALUE * 2 - 4;

    /**
     * 单元格行高
     */
    private static final int CELL_ROW_HEIGHT = 25;

    private final WorkbookInfo workbookInfo;

    /**
     * 初始化WorkBookInfo,并验证实例非空
     *
     * @param workbookInfo
     */
    public WorkbookExport(WorkbookInfo workbookInfo) {
        this.workbookInfo = ExceptionUtils.checkNotNull(workbookInfo, "workbookInfo");
    }

    /**
     * @param workbook
     * @param cols
     * @return
     */
    private List<ExportAdapterInfo> checkColumns(Workbook workbook, List<ColumnInfo> cols) {
        int size = cols.size();
        if (cols == null || size == 0) {
            throw new ExcelException("至少需要一列以上。");
        }
        List<ExportAdapterInfo> columns = new ArrayList<ExportAdapterInfo>();
        int index = 0;
        for (int i = 0; i < size; i++) {
            ColumnInfo column = cols.get(i);
            if (StringUtils.isNullOrBlank(column.getFriendlyName())) {
                throw new ExcelException(String.format("第{%d}列的 FriendlyName 为 null 或空白值", (i + 1)));
            }
            TupleTwo<List<ExportAdapterInfo>, Integer> tuple2 = column.createExportAdapters(workbook, index);
            index = tuple2.getItem2();
            columns.addAll(tuple2.getItem1());
        }
        if (!CollectionUtils.any(columns, (exportAdapterInfo) -> exportAdapterInfo != null)) {
            throw new ExcelException("至少需要一个导出模板列。");
        }
        return columns;
    }

    /**
     * 创建导出工作簿
     *
     * @param exportInfo
     * @param isImportTemplate
     * @return
     */
    public <T> Workbook createWorkbook(AbstractExportInfo<T> exportInfo, boolean isImportTemplate) {
        ExceptionUtils.checkNotNull(exportInfo, "exportInfo");
        List<T> list = exportInfo.getItems();
        if (list != null && list.size() > MAX_ROW_COUNT) {
            throw new ExcelException(String.format("记录数不能超过 %d 条", MAX_ROW_COUNT));
        }
        this.workbookInfo.check();
        this.workbookInfo.orderColumns();
        List<ColumnInfo> cols;
        int mergeCols, mergeRows;
        List<AbstractColumn> items = new ArrayList<AbstractColumn>();
        if (isImportTemplate) {
            cols = CollectionUtils.findCollection(this.workbookInfo.columnInfos(), (columninfo) -> columninfo.isImportColumn());
            if (!CollectionUtils.any(cols, (columninfo) -> columninfo != null)) {
                throw new ExcelException("输出导入模板时，至少需要一个导入列以上。");
            }
            mergeRows = 0;
            for (AbstractColumn column : cols) {
                items.add(column);
            }
            mergeCols = items.size();
        } else {
            cols = this.workbookInfo.columnInfos();
            mergeCols = workbookInfo.mergeCols();
            mergeRows = workbookInfo.mergeRows();
            items = this.workbookInfo.getColumns();
        }
        Workbook workbook = WorkbookFactory.createWorkbook(exportInfo);
        List<ExportAdapterInfo> columns = checkColumns(workbook, cols);
        Sheet sheet;
        if (StringUtils.isNullOrBlank(workbookInfo.getSheetName())) {
            sheet = WorkbookFactory.createSheets(workbook, 3);
        } else {
            sheet = workbook.createSheet(workbookInfo.getSheetName());
            workbook.createSheet(String.format("Sheet%d", 2));
            workbook.createSheet(String.format("Sheet%d", 3));
        }
        int beginIndex = 0;
        Row row;
        WorkbookHeader header = workbookInfo.getHeader();
        // 若不为空，创建工作薄大标题
        if (header != null) {
            row = header.createRow(sheet, HorizontalAlignment.CENTER, beginIndex, mergeCols, true);
            if (row != null) {
                beginIndex++;
            }
        }
        WorkbookHeader childHeader = workbookInfo.getChildHeader();
        // 若不为空，创建工作薄小标题
        if (childHeader != null) {
            row = childHeader.createRow(sheet, HorizontalAlignment.LEFT, beginIndex, mergeCols, false);
            if (row != null) {
                beginIndex++;
            }
        }
        beginIndex = createTitleRow(workbook, sheet, beginIndex, items, mergeRows, isImportTemplate);
        // 设置导出列数据
        createItems(workbook, sheet, beginIndex, columns, exportInfo);
        columns.clear();
        return workbook;
    }

    /**
     * 合并内容行信息
     */
    class MergeContentRowInfo {
        /**
         * 首行
         */
        private int firstRow;

        /**
         * 最后一行
         */
        private int lastRow;

        /**
         * 首个单元格值
         */
        private Object firstCellValue;

        /**
         * 获取首行
         *
         * @return
         */
        public int getFirstRow() {
            return firstRow;
        }

        /**
         * 设置首行
         *
         * @param firstRow
         */
        public void setFirstRow(int firstRow) {
            this.firstRow = firstRow;
        }

        /**
         * 获取 最后一行
         *
         * @return
         */
        public int getLastRow() {
            return lastRow;
        }

        /**
         * 设置 最后一行
         *
         * @param lastRow
         */
        public void setLastRow(int lastRow) {
            this.lastRow = lastRow;
        }

        /**
         * 获取首个单元格值
         *
         * @return
         */
        public Object getFirstCellValue() {
            return firstCellValue;
        }

        /**
         * 设置首个单元格值
         *
         * @param firstCellValue
         */
        public void setFirstCellValue(Object firstCellValue) {
            this.firstCellValue = firstCellValue;
        }

        /**
         * 获取合并数量
         *
         * @return
         */
        public int getMergeCount() {
            return lastRow - firstRow;
        }
    }

    /**
     * 创建项目集合
     *
     * @param workbook   工作薄
     * @param sheet
     * @param beginIndex
     * @param columns
     * @param exportInfo
     */
    private <T> void createItems(Workbook workbook, Sheet sheet, int beginIndex, List<ExportAdapterInfo> columns,
                                 AbstractExportInfo<T> exportInfo) {
        List<T> items = exportInfo.getItems();
        if (items != null && items.size() > 0 && columns.size() > 0) {
            int mergeContentRows = CollectionUtils.count(columns, item -> item.getColumnInfo().isMergeContentRow());
            if (mergeContentRows > 0 && items.size() > 1) {
                Map<ExportAdapterInfo, MergeContentRowInfo> mergeMap = new HashMap<ExportAdapterInfo, MergeContentRowInfo>(
                        16);
                for (T item : items) {
                    Row row = WorkbookFactory.createRow(sheet, beginIndex, CELL_ROW_HEIGHT);
                    for (ExportAdapterInfo column : columns) {
                        ColumnInfo columnInfo = column.getColumnInfo();
                        Object value = exportInfo.read(item, columnInfo.getPropertyName());
                        if (columnInfo.isMergeContentRow()) {
                            MergeContentRowInfo rowInfo = mergeMap.get(column);
                            if (rowInfo != null) {
                                if (cellValueEquals(value, rowInfo.firstCellValue,
                                        columnInfo.isMergeContentRow())) {
                                    rowInfo.setLastRow(row.getRowNum());
                                    column.createBlankCell(row);
                                } else {
                                    if (rowInfo.getMergeCount() > 0) {
                                        column.mergeRow(sheet, rowInfo.getFirstRow(),
                                                rowInfo.getLastRow());
                                    }
                                    rowInfo.setFirstRow(row.getRowNum());
                                    rowInfo.setFirstCellValue(value);
                                    rowInfo.setLastRow(row.getRowNum());
                                    column.createValueCell(row, value);
                                }
                            } else {
                                rowInfo = new MergeContentRowInfo();
                                rowInfo.setFirstRow(row.getRowNum());
                                rowInfo.setLastRow(row.getRowNum());
                                rowInfo.setFirstCellValue(value);
                                mergeMap.put(column, rowInfo);
                                column.createValueCell(row, value);
                            }
                        } else {
                            column.createValueCell(row, value);
                        }
                    }
                    beginIndex++;
                }
                for (Entry<ExportAdapterInfo, MergeContentRowInfo> entry : mergeMap.entrySet()) {
                    MergeContentRowInfo contentRowInfo = entry.getValue();
                    entry.getKey().mergeRow(sheet, contentRowInfo.getFirstRow(), contentRowInfo.getLastRow());
                }
                mergeMap.clear();
            } else {
                for (T item : items) {
                    Row row = WorkbookFactory.createRow(sheet, beginIndex, CELL_ROW_HEIGHT);
                    for (ExportAdapterInfo column : columns) {
                        Object read = exportInfo.read(item, column.getColumnInfo().getPropertyName());
                        column.createValueCell(row, read);
                    }
                    beginIndex++;
                }
            }
        }
    }

    /**
     * 单元格值 是否相等
     *
     * @param left
     * @param right
     * @param isBlank
     * @return
     */
    private Boolean cellValueEquals(Object left, Object right, Boolean isBlank) {
        String leftValue;
        String rightValue;
        if (left == null) {
            leftValue = "";
        } else {
            leftValue = left.toString();
        }
        if (right == null) {
            rightValue = "";
        } else {
            rightValue = right.toString();
        }
        if (isBlank) {
            return leftValue.trim().equals(rightValue.trim());
        }
        boolean result = leftValue.equals(rightValue);
        if (result && !isBlank && StringUtils.isNullOrBlank(leftValue)) {
            return false;
        }
        return result;
    }

    /**
     * 创建标题行
     *
     * @param workbook
     * @param sheet            工作表
     * @param beginIndex
     * @param columns
     * @param mergeRows
     * @param isImportTemplate
     * @return
     */
    private int createTitleRow(Workbook workbook, Sheet sheet, int beginIndex, List<AbstractColumn> columns,
                               int mergeRows, boolean isImportTemplate) {
        CellStyle titleCellStyle = WorkbookFactory.createCellStyle(workbook, HorizontalAlignment.CENTER, 10D, true,
                true);
        int index = beginIndex;
        if (mergeRows > 0) {
            beginIndex = beginIndex + mergeRows;
        }
        Map<Integer, Row> rowDic = new HashMap<Integer, Row>(64);
        for (int r = index; r <= beginIndex; r++) {
            rowDic.put(r, WorkbookFactory.createRow(sheet, r, 25));
        }
        int columnIndex = 0;

        for (AbstractColumn column : columns) {
            columnIndex = column.createTitleCell(sheet, titleCellStyle, columnIndex, index, mergeRows,
                    isImportTemplate);
        }
        return ++beginIndex;
    }
}
